#!/usr/bin/env python
# -*- coding: utf-8 -*-

NAME=u'斗地主'
CONF=dict(
    player_cards=[[3, 1], [4, 2]], 
    cardBases=[3, 2], 
)

HELP=u'''　　　　　　　　　　　　　　　斗　地　主

游戏简介：

　　“斗地主”是流行于湖北一带的一种扑克游戏，玩法简单，娱乐性强，老少皆宜。

详细规则、胜负判定方法：

　　该游戏由三或四个人玩，用二副牌，地主为一方，其余几家为另一方（成为农民），双方对战，先出完牌的一方获胜。

基本打法：
　　叫牌（暂不支持）：谁来当地主由叫牌来决定，地主将独自对抗两个农民。定约只有1分、2分和3分3种。发牌时谁发到翻开的牌谁就先叫。轮到叫牌的玩家可以不叫(Pass)，也可以叫出比当前定约更大的定约。如果3个玩家都不叫，那么本局牌就荒牌，并且重新发牌。如果有人叫了牌，那么叫牌就按逆时针继续(不叫或者叫出更高的定约)，直到连续两人不叫或者某人叫了3分(因为这是最高定约)叫牌才结束。最后一个叫得最高的定约者就成为地主，并且拿走3张牌面朝下的底牌，所以地主手上有20张牌。

　　发牌：三人玩一副牌（四人玩两副牌），发牌前需扣底，即由系统随即抽取三张牌（四人玩抽取八张牌）做为底牌，反扣在一边，其余的牌发给四家，底牌自动加到成为地主的人手中，三人玩底牌要亮出。

出牌：

　　首先将八张底牌交给地主，(这八张底牌并不向除地主之外的玩家公开)。然后由地主首先出牌，之后玩家按逆时针顺序依次出牌，直至某一方牌出完就结束此局。

牌型：

　　火箭：即双王(两副牌四王)，什么牌型都可打，是最大的牌。

　　炸弹：四张或四张以上同数值牌(如四个5或六个7)。除火箭和比自己大的炸弹外，什么牌型都可打。相同张数的炸弹，数值大的比数值小的大；张数多的炸弹比张数小的炸弹大。

　　单牌（一手牌）：单个牌。

　　对牌（一手牌）：数值相同的两张牌。

　　三张：数值相同的三张牌(如三个10)。

　　三带一（两副牌无）：数值相同的三张牌只给带一张单牌(如：333+4)。

　　三带一对：数值相同的三张牌只给带一对牌(如：333+44)。

　　四带二（两副牌无）：数值相同的四张牌只给带两张单牌(如：3333+48)。

　　四带二对（两副牌无）：数值相同的四张牌只给带两对牌(如：3333+4488)。

　　单顺：五张或更多的连续对牌(如：34567、456789)。

　　双顺：三对或更多的连续对牌(如：334455、66778899)。

　　三顺：二个或更多的连续三张牌(如：333444、444555666777)。

　　单飞（两副牌无）：多个三顺加上相同数量的一张单牌(如：333444+68)。

　　双飞：三顺＋同数量的对牌(如：333444+7799)。

牌型的大小：

　　火箭是最大的牌。炸弹，除火箭和比自己大的炸弹外，比其它牌型都大。对一般牌型而言，只有当牌型相同和总张数相同的牌，才可比较大小。其中三带二、双飞等组合牌型，只要比较其牌数最多牌值就行。只有比当前出的牌大的牌才能出。

　　牌的大小顺序为大王、小王、A、K、Q、J、10、9、8、7、6、5、4、3、2，不分花色。

得分：

　　一开始每家有100分，每次地主方的第一局的基本分为5分，以后每多赢一局加1分，最高10分。如果有一家小于等于0分，则游戏结束。
'''

import general
from server import *
from client import *

#游戏开始必须出的牌，最小牌列表，按牌基数排列
baseCards=[Card(DIAMOND, THREE), Card(DIAMOND, TWO)]

#牌型
CARDTYPE=[
    #非法，单张牌，一对牌，三张同数，三带一，三带一对，四带二，四带二对
    ILLEGAL, SINGLE, PAIR, TRIAD, TRIAD_SINGLE, TRIAD_PAIR, QUAD_TWO, QUAD_TWOPAIR, 
    #单顺，双顺，三顺，单飞，双飞，炸弹，火箭
    STRAIGHT, DOUBLE_STRAIGHT, TREBLE_STRAIGHT, PLANE, DOUBLE_PLANE, BOMB, ROCKET, 
]=range(15)

def orderCardByFace(ocards):
    '''牌按面值归类，包括特殊牌'''
    cardByFace={}
    for oc in ocards:
        face=oc.face
        if cardByFace.has_key(face):
            cardByFace[face].append(oc)
        else:
            cardByFace[face]=[oc]
    return cardByFace

def orderFaceByCount(cardByFace):
    '''面值按重牌数归类，包括特殊牌'''
    faceByCount={}
    for face, ocards in cardByFace.iteritems():
        ocount=len(ocards)
        if faceByCount.has_key(ocount):
            faceByCount[ocount].append(face)
        else:
            faceByCount[ocount]=[face]
    return faceByCount

def getSeqNum(faces):
    '''获得面值序列的连续数'''
    faces=sorted(faces)
    #连续值，1表示不连续
    seq=1
    #用于确定大小的基数面值
    baseface=faces[-1]
    
    count=len(faces)
    for i in range(count-1):
        if faces[i+1]-faces[i]==1:
            seq+=1
    
    if faces[0]==ACE:
        seq1=1
        #处理ACE作大的情况
        faces.append(faces.pop(0))
        for i in range(count-1):
            if (faces[i+1]-faces[i])%13==1:
                seq1+=1
        if seq1>seq:
            seq=seq1
            baseface=ACE
    return seq, baseface


def getCardType(cards, cardPack=1):
    '''公共方法，获得出牌类型，包括出牌基数'''
    count=len(cards)
    if count==0:
        type=ILLEGAL
        base=None
        return type, base

    elif count==1:
        #单张
        type=SINGLE
        base=cards[0]
        return type, base

    elif count==2:
        c0=Card(cards[0])
        c1=Card(cards[1])
        if c0.face==c1.face:
            #一对
            type=PAIR
            base=cards[0]
            return type, base
        elif c0.special and c1.special and c0.id!=c1.id and cardPack==1:
            #火箭（一副牌）
            type=ROCKET
            base=None
            return type, base
    
    elif count==3:
        ocards=[Card(cid) for cid in cards]
        if ocards[0].face==ocards[1].face==ocards[2].face:
            #三张
            type=TRIAD
            base=cards[0]
            return type, base

    elif count>=4:
        ocards=[Card(cid) for cid in cards]
        #牌按面值归类，包括特殊牌
        cardByFace=orderCardByFace(ocards)
        
        rjokers=cardByFace.get(RJOKER)
        bjokers=cardByFace.get(BJOKER)
        if rjokers and bjokers and len(rjokers)==len(bjokers)==2 and count==4 and cardPack==2:
            #火箭（两副）
            type=ROCKET
            base=None
            return type, base
        
        #面值按重牌数归类，包括特殊牌
        faceByCount=orderFaceByCount(cardByFace)
        
        #面值类型种数
        faceCount=len(cardByFace)
        #按面值类型种数判定
        if faceCount==1:
            #一种面值，炸弹
            type=BOMB
            base=cards[0]
            return type, base
        elif faceCount==2:
            #两种面值，可能是三带一、三带一对、三顺（2连）
            faces3=faceByCount.get(3)
            faces2=faceByCount.get(2)
            faces1=faceByCount.get(1)
            if faces3 and faces1 and len(faces3)==1 and len(faces1)==1 and cardPack==1:
                #三带一
                type=TRIAD_SINGLE
                face3=faces3[0]
                base=cardByFace[face3][0].id
                return type, base
            if faces3 and faces2 and len(faces3)==1 and len(faces2)==1:
                #三带一对
                type=TRIAD_PAIR
                face3=faces3[0]
                base=cardByFace[face3][0].id
                return type, base
        elif faceCount==3:
            #三种面值，可能是四带二、四带二对、双顺（3连）、三顺（3连）
            if cardPack==1:
                #一副牌时判断四张套路
                faces4=faceByCount.get(4)
                faces2=faceByCount.get(2)
                faces1=faceByCount.get(1)
                if faces4 and faces1 and len(faces4)==1 and len(faces1)==2:
                    #四带二
                    type=QUAD_TWO
                    face4=faces4[0]
                    base=cardByFace[face4][0].id
                    return type, base
                if faces4 and faces2 and len(faces4)==1 and len(faces2)==2:
                    #四带二对
                    type=QUAD_TWOPAIR
                    face4=faces4[0]
                    base=cardByFace[face4][0].id
                    return type, base

        #重牌数种数
        countCount=len(faceByCount)
        #按重牌数种数判定
        if countCount==1:
            #重叠数，该重叠数下的面值
            countx, facesx=faceByCount.items()[0]
            #获得连牌数
            seq, baseface=getSeqNum(facesx)
            #判定顺子
            if countx==1 and seq>=5:
                #单顺
                type=STRAIGHT
                base=cardByFace[baseface][0].id
                return type, base
            if countx==2 and seq>=3:
                #双顺
                type=DOUBLE_STRAIGHT
                base=cardByFace[baseface][0].id
                return type, base
            if countx==3 and seq>=2:
                #三顺
                type=TREBLE_STRAIGHT
                base=cardByFace[baseface][0].id
                return type, base
        elif countCount==2:
            #判定飞机
            faces3=faceByCount.get(3)
            faces2=faceByCount.get(2)
            faces1=faceByCount.get(1)
            if faces3 and faces1 and cardPack==1:
                seq, baseface=getSeqNum(faces3)
                if len(faces1)==seq>=2:
                    #单飞
                    type=PLANE
                    base=cardByFace[baseface][0].id
                    return type, base
            if faces3 and faces2:
                seq, baseface=getSeqNum(faces3)
                if len(faces2)==seq>=2:
                    #双飞
                    type=DOUBLE_PLANE
                    base=cardByFace[baseface][0].id
                    return type, base

    #否则不合法
    type=ILLEGAL
    base=None
    return type, base

UNKNOWN, LESS, EQUAL, BIGGER=range(4)
def compareCards(x, y, order, cardPack=1):
    '''公共方法，比较两组牌大小'''
    #获取各自的牌型
    cardtypex, basex=getCardType(x, cardPack)
    if cardtypex==ILLEGAL:
        #牌型不合法
        return UNKNOWN
    cardtypey, basey=getCardType(y, cardPack)
    if cardtypey==ILLEGAL:
        #牌型不合法
        return UNKNOWN
    
    if cardtypex!=cardtypey:
        #类型不等，是炸弹或火箭的大
        if cardtypex>cardtypey>=BOMB:
            #x是火箭
            return BIGGER
        elif cardtypey>cardtypex>=BOMB:
            #y是火箭
            return LESS
        elif cardtypex>=BOMB>cardtypey:
            return BIGGER
        elif cardtypey>=BOMB>cardtypex:
            return LESS
        else:
            return UNKNOWN
    else:
        #类型相等，比较base，炸弹还要比较长度
        if cardtypex==cardtypey==BOMB:
            if len(x)>len(y):
                return BIGGER
            elif len(x)<len(y):
                return LESS
            else:
                #相同长度的炸弹比较基数
                if order[basex]>order[basey]:
                    return BIGGER
                elif order[basex]<order[basey]:
                    return LESS
                else:
                    return EQUAL
        
        if cardtypex==cardtypey==ROCKET:
            #两个火箭相等（其实不可能出现，游戏里只有一个火箭）
            return EQUAL
        
        #其他类型长度相等时比较基数
        if len(x)==len(y):
            if order[basex]>order[basey]:
                return BIGGER
            elif order[basex]<order[basey]:
                return LESS
            elif order[basex]==order[basey]:
                return EQUAL
        
        #其他无法比较
        return UNKNOWN

def calcCards(selfcards, mcards, order, cardPack):
    '''计算出牌，暂时比较简单的出牌'''
    selfcards.sort(key=lambda x:order[x])
    selfocards=[Card(cid) for cid in selfcards]

    rockets=[]
    bombs=[]
    straights=[]
    straight2s=[]
    straight3s=[]

    #牌按面值归类，包括特殊牌
    cardByFace=orderCardByFace(selfocards)
    
    #收集火箭
    rjokers=cardByFace.get(RJOKER)
    bjokers=cardByFace.get(BJOKER)
    if rjokers and bjokers:
        if len(rjokers)==len(bjokers)==2 and cardPack==2:
            #有火箭（两副）
            rockets.append([BJOKER, BJOKER, RJOKER, RJOKER])
            del cardByFace[RJOKER]
            del cardByFace[BJOKER]
        elif len(rjokers)==len(bjokers)==1 and cardPack==1:
            #有火箭（一副）
            rockets.append([BJOKER, RJOKER])
            del cardByFace[RJOKER]
            del cardByFace[BJOKER]
    
    #面值按重牌数归类，包括特殊牌
    faceByCount=orderFaceByCount(cardByFace)

    #收集炸弹
    for count, faces in faceByCount.iteritems():
        if count>=4:
            for face in faces:
                cards=[oc.id for oc in cardByFace[face]]
                bombs.append(cards)
                del cardByFace[face]

    mcardtype, mbase=getCardType(mcards)
    
    if mcardtype==ROCKET:
        #应付火箭
        return []
    elif mcardtype in [SINGLE, PAIR, TRIAD]:
        #应付单牌、对牌、三条
        cardcount=mcardtype-SINGLE+1
        faces=faceByCount.get(cardcount)
        if faces:
            for face in faces:
                cards=[oc.id for oc in cardByFace[face]]
                if compareCards(cards, mcards, order, cardPack)==BIGGER:
                    #有大即返回
                    return cards
        #否则随机出炸弹、火箭或不出
        outcards=[[]]+bombs+rockets
        return random.choice(outcards)
    elif mcardtype!=ILLEGAL:
        #TODO:应付其它
        outcards=bombs+rockets
        for cards in outcards:
            if compareCards(cards, mcards, order, cardPack)==BIGGER:
                #有大即返回
                return cards
        #否则返回空
        return []

    #非法代表可以任意出（非空）
    elif ILLEGAL==mcardtype:
        outcards=bombs+rockets
        faces=[]
        for i in [1, 2, 3]:
            facex=faceByCount.get(i)
            if facex:
                faces+=facex
        for face in faces:
            cards=[oc.id for oc in cardByFace[face]]
            outcards.append(cards)
        return random.choice(outcards)

    #无牌可出
    return []


class Game(general.Game):
    '''游戏类，斗地主'''
    def __init__(self):
        super(Game, self).__init__()
        
        #游戏模块名称
        self.moduleName='DouDiZhu'
        #游戏名称
        self.name=NAME
    
    def setCardOrder(self, base=3):
        '''设置排序'''
        order=range(13)
        for i in range(base-1):
            c=order.pop()
            order.insert(0, c)
        order=order*4+[100, 101]
        self.cardOrder=order
    
    def configure(self):
        '''追加设置'''
        super(Game, self).configure()
        #设置基数牌
        bcIndex=CONF['cardBases'].index(self.cardBase)
        self.baseCard=baseCards[bcIndex]
        #设置牌序
        self.setCardOrder(self.cardBase)
        #设置牌堆
        self.table.setPileCards(range(54)*self.cardPack)
        #设置积分盒
        self.table.fillScoreBox([100 for i in range(self.playerCount)])
        
        if self.cardPack==1:
            self.reserveCardCount=3
        elif self.cardPack==2:
            self.reserveCardCount=8
        
        #地主id
        self.landlord=0
        #初始加分
        self.score=5

    def notifyInitGameData(self):
        '''通知更新初始游戏数据'''
        specdata={
            'landlord':self.landlord, 
        }
        super(Game, self).notifyInitGameData(specdata)

    def notifyGameData(self):
        '''通知更新初始游戏数据'''
        specdata={
            'landlord':self.landlord, 
        }
        super(Game, self).notifyGameData(specdata)

    def calcScore(self):
        '''计算玩家积分'''
        winId=self.tokenPlayerId
        if winId==self.landlord and self.score<10:
            score=self.score
            self.score+=1
        else:
            score=-self.score
        
        for i in range(len(self.players)):
            if i==self.landlord:
                self.scoreBox[i]+=score
            else:
                self.scoreBox[i]-=score

    def deal(self):
        '''发牌，较为通用的程序'''
        self.notifyAll(SERVERID, SHOWMESSAGE, u'***打牌***')
        i=self.markPlayerId
        while len(self.cardBox)>self.reserveCardCount:
            self.tokenPlayerId=i
            self.notifyGameData()
            player=self.players[i]
            card=self.cardBox.pop()
            cards=[card]
            cmd_data=player.do(TAKECARD, cards)
            if not cmd_data:
                #连接错误，终止游戏
                raise Exception('game error')
            #通知各玩家
            self.notifyAll(player, TAKECARD)
            #按座位顺序轮流摸牌
            i+=1
            i%=self.playerCount
        
        #TODO:随机选择地主
        self.landlord=random.choice(range(self.playerCount))
        for player in self.players:
            id=player.info['id']
            if id==self.landlord:
                player.setInfo({'role':u'地主'})
            else:
                player.setInfo({'role':u'农民'})
        self.markPlayerId=self.landlord
        reserveCards=self.cardBox[:]
        player=self.players[self.landlord]
        
        self.notifyAll(SERVERID, SHOWMESSAGE, u'地主: Player %d'%player.info['id'])
        
        while self.cardBox:
            self.tokenPlayerId=self.landlord
            self.notifyGameData()
            card=self.cardBox.pop()
            cards=[card]
            cmd_data=player.do(TAKECARD, cards)
            if not cmd_data:
                #连接错误，终止游戏
                raise Exception('game error')
            #通知各玩家
            self.notifyAll(player, TAKECARD)
        
        if self.cardPack==1:
            self.notifyAll(SERVERID, SHOWMESSAGE, u'***亮底牌***')
            self.notifyAll(player, SHOWCARD, reserveCards)
            time.sleep(3)
            self.clearOutList()

    def judge(self, player, data):
        '''出牌有效判定'''
        #先排序
        data.sort(key=lambda x:self.cardOrder[x])
        if self.markPlayerId==player.info['id']:
            #上次被标记的是自己，表示这轮没人跟牌，允许任意出（不允许空）
            if not data:
                #不出牌
                return UNDO
            cardtype, base=getCardType(data, self.cardPack)
            if cardtype==ILLEGAL:
                #牌型不合法
                return UNDO
            return OK
        
        if not data:
            #不出牌，允许
            return OK

        #取得上次被标记玩家出的牌（大牌）和牌型
        mcards=self.outList[self.markPlayerId]
        r=compareCards(data, mcards, self.cardOrder, self.cardPack)
        if r!=BIGGER:
            #牌型不对大牌者或不大于大牌者
            return UNDO
        
        #合法可出，出完后标记该玩家
        self.setMarkPlayerId(player)
        return OK

class AI(general.AI):
    '''斗地主游戏的AI'''
    def __init__(self):
        super(AI, self).__init__()
    
    def initData(self):
        '''初始化游戏数据'''
        #获取基数牌
        cardBase=self.player.gameData['cardBase']
        bcIndex=CONF['cardBases'].index(cardBase)
        self.baseCard=baseCards[bcIndex]
        #获取牌序
        self.cardOrder=self.player.gameData['cardOrder']
        #获取牌副数
        self.cardPack=self.player.gameData['cardPack']
    
    def playCard(self):
        '''实现出牌提示逻辑，这里是尽量出可出的牌'''
        #获得地主id
        self.landlord=self.player.gameData['landlord']
        
        markPlayerId=self.player.gameData['markPlayerId']
        if markPlayerId==self.player.info['id']:
            #上次被标记的是自己（大），此次任意出牌
            cards=calcCards(self.player.cardList, [], self.cardOrder, self.cardPack)
            return cards

        #取得上次被标记玩家出的牌（大牌）
        outList=self.player.gameData['outList']
        mcards=outList[markPlayerId]
        cards=calcCards(self.player.cardList, mcards, self.cardOrder, self.cardPack)
        return cards


if __name__ == "__main__":
    print getSeqNum([8, 9, 10, 11, 12, 0])
    print getSeqNum([1, 2, 3, 4, 5, 6, 0])
    
    cardsList=[
        [Card(CLUB, TWO), Card(CLUB, THREE), Card(HEART, FIVE), Card(CLUB, FOUR)],
        [Card(CLUB, THREE)], 
        [Card(CLUB, THREE), Card(HEART, THREE)], 
        [Card(CLUB, THREE), Card(HEART, THREE), Card(DIAMOND, THREE)], 
        [Card(HEART, FIVE), Card(DIAMOND, FIVE), Card(CLUB, FIVE), Card(SPADE, ACE)],
        [Card(CLUB, ACE), Card(DIAMOND, FIVE), Card(CLUB, FIVE), Card(SPADE, ACE), Card(SPADE, FIVE)], 
        [Card(CLUB, ACE), Card(CLUB, ACE), Card(CLUB, ACE), Card(CLUB, ACE), Card(SPADE, SEVEN), Card(CLUB, SIX)], 
        [Card(CLUB, ACE), Card(CLUB, ACE), Card(CLUB, ACE), Card(CLUB, ACE), Card(SPADE, SEVEN), Card(CLUB, SIX), Card(SPADE, SEVEN), Card(CLUB, SIX)], 
        [Card(CLUB, ACE), Card(CLUB, THREE), Card(DIAMOND, FIVE), Card(SPADE, FOUR), Card(DIAMOND, TWO), Card(CLUB, SIX), Card(CLUB, SEVEN)], 
        [Card(CLUB, TWO), Card(CLUB, THREE), Card(CLUB, TWO), Card(HEART, THREE), Card(CLUB, FOUR), Card(CLUB, FOUR)],
        [Card(CLUB, TWO), Card(CLUB, THREE), Card(CLUB, TWO), Card(HEART, THREE), Card(CLUB, TWO), Card(CLUB, THREE)],
        [Card(CLUB, ACE), Card(CLUB, KING), Card(CLUB, ACE), Card(HEART, ACE), Card(CLUB, KING), Card(CLUB, KING), Card(SPADE, SEVEN), Card(CLUB, SIX)],
        [Card(CLUB, ACE), Card(CLUB, TWO), Card(CLUB, ACE), Card(HEART, ACE), Card(CLUB, TWO), Card(CLUB, TWO), Card(SPADE, SEVEN), Card(CLUB, SIX), Card(SPADE, SEVEN), Card(CLUB, SIX)],
        [Card(CLUB, ACE), Card(CLUB, ACE), Card(CLUB, ACE), Card(CLUB, ACE), Card(CLUB, ACE)], 
        [Card(CLUB, TWO), Card(CLUB, TWO), Card(CLUB, TWO), Card(CLUB, TWO), ], 
        [Card(BJOKER), Card(RJOKER),],
        [Card(BJOKER), Card(RJOKER), Card(BJOKER), Card(RJOKER),],
]

    for ocards in cardsList:
        cards=[c.id for c in ocards]
        type, base=getCardType(cards)
        print type, 
        if base: print Card(base), 
        print
    
    ocards=cardsList[-1]
    cards=[c.id for c in ocards]
    type, base=getCardType(cards, 2)
    print type, 
    if base: print Card(base), 
    print
    
    cardsx=[c.id for c in cardsList[-4]]
    cardsy=[c.id for c in cardsList[-3]]
    print compareCards(cardsx, cardsy, range(13)*4+[100, 101])

